/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */

#include "Application.h"
#include <lib3d/selector/Simple.h>
#include <lib3d/material/TextureSimple.h>
#include <lib3d/material/TextureAtlas.h>
#include <lib3d/material/TextureReflection.h>
#include <lib3d/material/TextureLoaderNotifier.h>
#include <lib3d/model/Model.h>
#include <lib3d/node/ModelFixed.h>
#include <lib3d/node/CameraPerspective.h>
#include <lib3d/model/ModelInHardware.h>
#include <input/hard/marmalade/Creator.h>

namespace liba
{
namespace lib3d
{

Application::Application(const std::string & caption, const std::string & cmd_line)
:	ApplicationWindowed( caption, cmd_line ),
	cursor_camera( 0 ),
	cursor_node( 0 ),
	cursor_root( new node::Root(Atom("lib3d::Application::cursor_root")) ),
	previous_total_reps_to_load( 0 ),
	loading_screen_delay(1000),
	hardware( 0 )
{

	filesystem::String settings_filename = filesystem::String("%local_appdata%/") + caption.c_str() + "/videosettings.ini";
	selector::Simple sel;

	hardware = sel.load(settings_filename);

	if( !hardware || !hardware->set_mode( get_window() ) )
	{
		sel.change_mode( get_window() );
		hardware = sel.set_mode( get_window() );
	}

	if( !hardware )
		THROW(Gluck(__FUNCTION__, "Could not create desired videomode"));

	show_load_animation = false;
}

Application::Application(const std::string & caption, int sx, int sy, int resizeable )
:	ApplicationWindowed( caption, sx, sy, resizeable ),
	cursor_camera( 0 ),
	cursor_node( 0 ),
	cursor_root( new node::Root(Atom("lib3d::Application::cursor_root")) ),
	previous_total_reps_to_load( 0 ),
	hardware( 0 )
{
	filesystem::String settings_filename = filesystem::String("%local_appdata%/") + caption.c_str() + "/videosettings.ini";

	selector::Simple sel;
	hardware = sel.load(settings_filename);

	if( !hardware || !hardware->set_mode( get_window() ) )
	{
		sel.change_mode( get_window() );
		hardware = sel.set_mode( get_window() );
	}

	if( !hardware )
		THROW(Gluck(__FUNCTION__, "Could not create desired videomode"));

	show_load_animation = false;
}

Application::~Application()
{
	delete cursor_root; cursor_root = 0;
	
	if( hardware )
		hardware->stop_mode();
	hardware = 0;
}

bool Application::LifeCycle( time_float delta_time )
{	
	cursor_animation.add_time_cycle( delta_time );
	if( cursor_node && cursor_camera )
	{
		double rel_x = 0;
		double rel_y = 0;

		Vector pos = cursor_node->GetPosition();

		double aspect( 1.0 );
		double fov( 1.0 );
		bool result( true );
		result = result && cursor_camera->FindProperty<double>( node::CameraPerspective::aspect_name );
		if ( result )
			aspect = cursor_camera->FindProperty<double>( node::CameraPerspective::aspect_name )->Get();
		result = result && cursor_camera->FindProperty<double>( node::CameraPerspective::fov_name );
		if ( result )
			fov = cursor_camera->FindProperty<double>( node::CameraPerspective::fov_name )->Get();
		if( result )
		{
			double h = ( cos( fov/2 ) / sin( fov/2 ) );
			double w = aspect * h;
			pos[0] = pos.z * (rel_x - 0.5) * 2 / w;
			pos[1] = pos.z * (rel_y - 0.5) * 2 / h;
		}
		else
		{
			double width( 0.0 );
			double height( 0.0 );
			bool result( true );
			result = result && cursor_camera->FindProperty<double>( node::CameraParallel::width_name );
			if ( result )
				width = cursor_camera->FindProperty<double>( node::CameraParallel::width_name )->Get();
			result = result && cursor_camera->FindProperty<double>( node::CameraParallel::height_name );
			if ( result )
				height = cursor_camera->FindProperty<double>( node::CameraParallel::height_name )->Get();

			if ( !result )
			{
				cursor_camera->FindProperty<double>( node::CameraParallel::width_name )->Set( width );
				cursor_camera->FindProperty<double>( node::CameraParallel::height_name )->Set( height );
			}

			pos[0] = (rel_x  - 0.5)* width;
			pos[1] = (0.5 - rel_y) * height;
		}
		cursor_node->ChangePosition( pos );
	}

	material::TextureLoadingManager::GetInstance()->LifeCycle();

	int total_reps_to_load = 0;

	__int64 start_load_time = threads::get_precise_tick_count();
	float time_to_load = show_load_animation ? 0.1 : 1;

	total_reps_to_load += material::TextureSimple::load_reps(start_load_time, time_to_load);
	total_reps_to_load += material::TextureAtlas::load_reps(start_load_time, time_to_load);
	total_reps_to_load += material::TextureReflection::load_reps(start_load_time, time_to_load);
	total_reps_to_load += SharedPrecached<filesystem::String, model::ModelInHardware>::load_reps(start_load_time, time_to_load);
	total_reps_to_load += model::ModelInHardware::load_reps(start_load_time, time_to_load);

	total_reps_to_load += material::TextureSequence::load_reps(start_load_time, time_to_load);
	total_reps_to_load += material::TextureSequenceFrame::load_reps(start_load_time, time_to_load);

	static __int64 time_load_start = 0;
	if( !show_load_animation && total_reps_to_load != 0 )
	{
		if(!time_load_start)
			time_load_start = threads::get_precise_tick_count();
		else
		{
			int delta = (threads::get_precise_tick_count() - time_load_start);
			show_load_animation = delta > loading_screen_delay;
		}

		if(show_load_animation)
			on_loading_start();
	}
	previous_total_reps_to_load = std::max( previous_total_reps_to_load, total_reps_to_load );
	if( show_load_animation && previous_total_reps_to_load != 0 )
	{
		on_loading_progress( double(previous_total_reps_to_load - total_reps_to_load)/previous_total_reps_to_load );
	}
	if( previous_total_reps_to_load != 0 && total_reps_to_load == 0 )
	{
		time_load_start = 0;
		if(show_load_animation)
		{
			show_load_animation = false;
			on_loading_finish();
		}
		previous_total_reps_to_load = 0;
	}

	return ApplicationWindowed::LifeCycle( delta_time );
}

int Application::window_proc( unsigned msg, unsigned wParam, unsigned lParam )
{
	return 0;
}

void Application::change_cursor(const filesystem::String & scene)
{
	if( cursor_scene == scene )return;
	cursor_scene = scene;
	cursor_camera = 0;
	cursor_node = 0;
	cursor_root->Clear();
	cursor_animation = animate::Animation();
	if( scene != filesystem::String() )
	{
		cursor_camera = load_root( cursor_root, &cursor_animation, scene, Atom("Camera") );
		cursor_node = cursor_root->FindNode( Atom("Cursor"), true );
		if( !cursor_node )
			THROW(GluckBadResource(__FUNCTION__, "Cursor node not found", scene + ".scene" ));
	}
}

const filesystem::String Application::get_cursor()const
{
	return cursor_scene;
}

void Application::render_cursor( hard::RenderTarget * rt )
{
	if( cursor_camera )
	{
		lib3d::hard::RenderList render_list;
		cursor_camera->start_render_to_list( hardware, &render_list );
		render_list.clear_render_target = false;
		hardware->render( &render_list, rt );
	}
}

node::Camera * Application::load_root(node::Root * root, animate::Animation * animation, const filesystem::String & base_name, const Atom & camera_name )
{
	if( !root )
		return 0;
	boost::scoped_ptr<resource::Resource> scene_res(resource::Manager::create_resource( base_name + ".scene" ));
	if( !xml::StreamResource( scene_res.get() ).load( &xml::Root(root,"Scene") ) )
		THROW(GluckBadResource(__FUNCTION__, "load() failed", base_name + ".scene"));

	node::Camera * camera = root->find_camera( camera_name );
	if( !camera )
		THROW(GluckBadResource(__FUNCTION__, "Camera not found", base_name + ".scene"));

	if( animation )
	{
		boost::scoped_ptr< resource::Resource > anim_res(resource::Manager::create_resource( base_name + ".anim" ));
		if( !xml::StreamResource( anim_res.get() ).load( &xml::Root(animation, "Animation") ) )
			THROW(GluckBadResource(__FUNCTION__, "load() failed", base_name + ".anim"));
		if( !animation->bind( root, false ) )
		{}//	throw GluckBadResource(__FUNCTION__, "bind failed", base_name + ".anim");
	}
	return camera;
}

} // namespace lib3d
} // namespace liba

